package HTML::FormFu::Constraint::_others;

use strict;
use base 'HTML::FormFu::Constraint';
use Class::C3;

use HTML::FormFu::Util qw(
    DEBUG_CONSTRAINTS
    debug
);
use List::MoreUtils qw( any none );
use Storable qw( dclone );
use Carp qw( croak );

__PACKAGE__->mk_item_accessors( qw(
    attach_errors_to_base
    attach_errors_to_others
) );

__PACKAGE__->mk_accessors( qw(
    others
    attach_errors_to
) );

sub mk_errors {
    my ( $self, $args ) = @_;

    my $pass   = $args->{pass};
    my @failed = $args->{failed} ? @{ $args->{failed} } : ();
    my @names  = $args->{names}  ? @{ $args->{names} }  : ();

    my $force  = $self->force_errors || $self->parent->force_errors;

    DEBUG_CONSTRAINTS && debug(PASS => $pass);
    DEBUG_CONSTRAINTS && debug(NAMES => \@names);
    DEBUG_CONSTRAINTS && debug('FAILED NAMES' => \@failed);
    DEBUG_CONSTRAINTS && debug(FORCE => $force);
    
    if ( $pass && !$force ) {
        DEBUG_CONSTRAINTS && debug('constraint passed, or force_errors is false - returning no errors');
        return;
    }
    
    my @can_error;
    my @has_error;

    if ( $self->attach_errors_to ) {
        push @can_error, @{ $self->attach_errors_to };
        
        if ( !$pass ) {
            push @has_error, @{ $self->attach_errors_to };
        }
    }
    elsif ( $self->attach_errors_to_base ) {
        push @can_error, $self->nested_name;
        
        if ( !$pass ) {
            push @has_error, $self->nested_name;
        }
    }
    elsif ( $self->attach_errors_to_others ) {
        push @can_error,
              ref $self->others ? @{ $self->others }
            :                     $self->others
            ;
        
        if ( !$pass ) {
            push @has_error,
                  ref $self->others ? @{ $self->others }
                :                     $self->others
                ;
        }
    }
    else {
        push @can_error, @names;
        
        if ( !$pass ) {
            push @has_error, @failed;
        }
    }

    DEBUG_CONSTRAINTS && debug('CAN ERROR' => \@can_error);
    DEBUG_CONSTRAINTS && debug('HAS ERROR' => \@has_error);

    my @errors;

    for my $name (@can_error) {
        
        next unless $force || grep { $name eq $_ } @has_error;
        
        DEBUG_CONSTRAINTS && debug('CREATING ERROR' => $name);
        
        my $field = $self->form->get_field( { nested_name => $name } )
            or die "others() field not found: '$name'";

        my $error = $self->mk_error;

        $error->parent($field);

        if ( !grep { $name eq $_ } @has_error ) {
            DEBUG_CONSTRAINTS && debug("setting '$name' error forced(1)");
            
            $error->forced(1);
        }

        push @errors, $error;
    }
    
    return @errors;
}

sub mk_auto_js_errors {
    my ( $self, $args ) = @_;
    
    my $others = $self->others;
    
    $others = [$others]
        if ref $others ne 'ARRAY';
    
    return $self->next::method({
        names => $others,
        $args ? %$args : (),
    });
}

sub auto_js_signature {
    my ($self) = @_;
    
    croak 'auto_js_signature() method is not a setter'
        if @_ > 1;
    
    return 'constraint_id, field_id, other_ids, error_field_ids, message_ids, container_ids';
}

sub auto_js_args {
    my ( $self, $render ) = @_;
    
    croak 'element $render argument required'
        if !defined $render;
    
    my $form = $self->form;
    
    # need to find all fields with errors from this constraint
    my $others = $self->others;
    $others = [$others] if ref $others ne 'ARRAY';
    
    my @others;
    for my $other ( @$others ) {
        push @others, $form->get_field({ nested_name => $other });
    }
    
    my @fields;
    
    for my $field ( $self->parent, @others ) {
        my %field = ( field => $field );
        
        for my $error ( @{ $field->get_errors({ forced => 1 }) } ) {
            
            if ( $error->processor == $self ) {
                $field{error} = $error;
                last;
            }
        }
        
        push @fields, \%field;
    }

    # candidate for improvement...
    # need to re-call render_data for all other fields
    
    for my $field (@fields) {
        if ( $field->{field} == $self->parent ) {
            $field->{render} = $render;
        }
        else {
            $field->{render} = $field->{field}->render_data_non_recursive;
        }
    }
    
    my @other_ids;
    my @error_ids;
    my @message_ids;
    my @container_ids;
    
    for my $field (@fields) {
        # rebuild 'others' list
        if ( $self->parent != $field->{field} ) {
            push @other_ids, $field->{render}{attributes}{id};
        }
        
        # match error objects
        for my $error ( @{ $field->{render}{errors} } ) {
            if ( $error->{object} == $field->{error} ) {
                
                push @error_ids, $field->{render}{attributes}{id};
                
                push @message_ids, $error->{container_attributes}{id};
                
                push @container_ids, $field->{render}{container_attributes}{id};
                last;
            }
        }
        
    }
    
    my $other_ids = sprintf "[%s]",
        join ',',
        map { "'$_'" }
            @other_ids;
    
    my $error_ids = sprintf "[%s]",
        join ',',
        map { "'$_'" }
            @error_ids;
    
    my $message_ids = sprintf "[%s]",
        join ',',
        map { "'$_'" }
            @message_ids;
    
    my $container_ids = sprintf "[%s]",
        join ',',
        map { "'$_'" }
            @container_ids;
    
    return sprintf "'%s', '%s', %s, %s, %s, %s",
        $self->auto_js_id,
        $render->{attributes}{id},
        $other_ids,
        $error_ids,
        $message_ids,
        $container_ids;
}

sub auto_js_method_call {
    my ( $self, $render ) = @_;
    
    croak 'element $render argument required'
        if !defined $render;
    
    return sprintf "%s(%s); ",
        $self->auto_js_method_name,
        $self->auto_js_args( $render );
}

sub clone {
    my $self = shift;

    my $clone = $self->next::method(@_);

    if ( ref $self->others ) {
        $clone->others( dclone $self->others );
    }

    return $clone;
}

1;

__END__

=head1 NAME

HTML::FormFu::Constraint::_others - Base class for constraints needing others() method

=head1 METHODS

=head2 others

Arguments: \@nested_names

=head2 attach_errors_to_base

If true, any error will cause the error message to be associated with the 
field the constraint is attached to.

Can be use in conjunction with L</attach_errors_to_others>.

Is ignored if L</attach_errors_to> is set.

=head2 attach_errors_to_others

If true, any error will cause the error message to be associated with every 
field named in L</others>.

Can be use in conjunction with L</attach_errors_to_base>.

Is ignored if L</attach_errors_to> is set.

=head2 attach_errors_to

Arguments: \@field_names

Any error will cause the error message to be associated with every field 
named in L</attach_errors_to>.

Overrides L</attach_errors_to_base> and L</attach_errors_to_others>.

=head1 SEE ALSO

Is a sub-class of, and inherits methods from L<HTML::FormFu::Constraint>

L<HTML::FormFu>

=head1 AUTHOR

Carl Franks C<cfranks@cpan.org>

=head1 LICENSE

This library is free software, you can redistribute it and/or modify it under
the same terms as Perl itself.
